#ifndef __tenacitas_log__logger__h__
#define __tenacitas_log__logger__h__


// =============================================================================
/**
@file 
This file defines the @p logger class
*/


// =============================================================================
//                                 C++ Headers

#include <iostream>
#include <fstream>
#include <memory>
//#include <mutex>
//#include <thread>
#include <sstream>
#include <cassert>
#include <ctime>

// =============================================================================
//                                 3rds Headers

#include <boost/thread/thread.hpp>
#include <boost/thread/mutex.hpp>

// =============================================================================
//                                 Our Headers


// =============================================================================
//                                 Namespaces
using namespace std;


// =============================================================================
//                                 Macro Contants

/**
*/


// =============================================================================
//                                 Macro Commands

/** Logs a message that a function is starting
@param [in] logger is a specialization of <tt>logger<t_printer></tt>
*/ 
#define log_begin(logger) logger->trace( std::string( __FILE__) , std::string( __FUNCTION__ ), __LINE__ ) << string( "IN" )

/** Logs a message that a function is ending
@param [in] logger is a specialization of <tt>logger<t_printer></tt>
*/ 
#define log_end(logger) logger->trace( std::string( __FILE__) , std::string( __FUNCTION__ ), __LINE__ ) << string( "OUT" )

/** Logs a message if the log level is trace
@param [in] logger is a specialization of <tt>logger<t_printer></tt>
*/ 
#define log_trace(logger) logger->trace( std::string( __FILE__) , std::string( __FUNCTION__ ), __LINE__ ) 

/** Logs a message if the log level is debug
@param [in] logger is a specialization of <tt>logger<t_printer></tt>
*/ 
#define log_debug(logger) logger->debug( std::string( __FILE__) , std::string( __FUNCTION__ ), __LINE__ ) 

/** Logs a message if the log level is warning
@param [in] logger is a specialization of <tt>logger<t_printer></tt>
*/
#define log_warn(logger) logger->warn( std::string( __FILE__) , std::string( __FUNCTION__ ), __LINE__ )

/** Logs a message if the log level is info
@param [in] logger is a specialization of <tt>logger<t_printer></tt>
*/
#define log_info(logger) logger->info( std::string( __FILE__) , std::string( __FUNCTION__ ), __LINE__ )

/** Logs a message if the log level is error
@param [in] logger is a specialization of <tt>logger<t_printer></tt>
*/
#define log_error(logger) logger->error( std::string( __FILE__) , std::string( __FUNCTION__ ), __LINE__ )

/** Logs a message if the log level is critical
@param [in] logger is a specialization of <tt>logger<t_printer></tt>
*/
#define log_critical(logger) logger->critical( std::string( __FILE__) , std::string( __FUNCTION__ ), __LINE__ )



/**
namespace of the organization
*/
namespace tenacitas {
    /**
    namespace for the project
    */
    namespace log {

        // =====================================================================
        //                             Typedefs

        /**
        */


        // =====================================================================
        //                             Pre-Declarations


        // =====================================================================
        //                             Attributes

        /**
        */


        // =====================================================================
        //                             Functions

        /**
        */


        // =====================================================================
        //                             Classes


        /** Prints log messages to cout */
        class cout_printer {
        public:

            // -----------------------------------------------------------------
            //    cout_printer - Public Operators

            template <typename t_type>
            void print( const t_type & p_value ) {
                boost::lock_guard<boost::mutex> l_lock( m_mutex );
                cout << p_value;
                cout.flush( );
            }

        private:
            boost::mutex m_mutex;
        };

        /** Prints log messages to cerr */
        class cerr_printer {
        public:

            // -----------------------------------------------------------------
            //    cerr_printer - Public Operators

            template <typename t_type>
            void print( const t_type & p_value ) {
                boost::lock_guard<boost::mutex> l_lock( m_mutex );
                cerr << p_value;
                cerr.flush( );
            }

        private:
            boost::mutex m_mutex;
        };

        /** Prints log messages to a file */
        class  file_printer {
        public:

            // -----------------------------------------------------------------
            //    file_printer - Public Constructors

            /** Constructor

            @param [in] p_file_name defines the name of the file where the log 
            messages will be printed to
            */
            file_printer( const std::string & p_file_name );

            // -----------------------------------------------------------------
            //    FilePrinter - Public Operators

            template <typename t_type>
            void print( const t_type & p_value ) {
                boost::lock_guard<boost::mutex> l_lock( m_mutex );
                m_file << p_value;
                m_file.flush( );                
            }

        private:
            boost::mutex m_mutex;            
            std::fstream m_file;
        };


        /** Defines a base template class for logging messages

        Please, read the @example main.cpp file
        for examples on how to use the @p logger class

        @nosubgrouping 
        */

        template <typename t_printer>
        class logger {

        public:

            typedef t_printer printer_t;

            typedef std::shared_ptr<printer_t> printer_ptr_t;

            typedef typename std::shared_ptr<logger<printer_t> > ptr_t;

            // -----------------------------------------------------------------
            //    logger - Public Internal Types

            /** The possible log levels */
            typedef enum { trace_level = 0,
                debug_level = 1,
                warn_level = 2,
                info_level = 3,
                error_level = 4,
                critical_level = 5,
                no_log_level = 99 }  level_type;

            // -----------------------------------------------------------------
            //    logger - Public Constructors

            /** Constructor */
            logger( printer_ptr_t p_printer, level_type p_level = warn_level );


            /**
            Destructor
            */
            ~logger( );

            // -----------------------------------------------------------------
            //    logger - Public Accessors

            ///** Defines a new log level */
            //void level( level_type p_level );

            /** Retrieves the current log level */
            const level_type & level( ) const;

            // -----------------------------------------------------------------
            //    logger - Public Processors

            /** Prints a trace message

            This method should be not used directly. Please, use the @p
            log_begin, @p log_end, or @p log_trace macro command

            @param [in] p_file is the name of the file from where the log
            message is being printed
            @param [in] p_function is the name of the function from where the
            log message is being printed
            @param [in] p_line is the line number from where the log message
            is being printed

            @return the output stream used to print the message
            */
            logger & trace( const std::string &p_file,
                const std::string &p_function,
                int32_t p_line );

            /** Prints a debug message

            This method should be not used directly. Please, use the @p
            log_debug macro command

            @param [in] p_file is the name of the file from where the log
            message is being printed
            @param [in] p_function is the name of the function from where the
            log message is being printed
            @param [in] p_line is the line number from where the log message
            is being printed

            @return the output stream used to print the message
            */
            logger & debug( const std::string &p_file,
                const std::string &p_function,
                int32_t p_line );

            /** Prints a warning message

            This method should be not used directly. Please, use the @p
            log_warn macro command

            @param [in] p_file is the name of the file from where the log
            message is being printed
            @param [in] p_function is the name of the function from where the
            log message is being printed
            @param [in] p_line is the line number from where the log message
            is being printed

            @return the output stream used to print the message
            */
            logger & warn( const std::string &p_file,
                const std::string &p_function,
                int32_t p_line );

            /** Prints an info message

            This method should be not used directly. Please, use the @p
            log_info macro command

            @param [in] p_file is the name of the file from where the log
            message is being printed
            @param [in] p_function is the name of the function from where the
            log message is being printed
            @param [in] p_line is the line number from where the log message
            is being printed

            @return the output stream used to print the message
            */
            logger & info( const std::string &p_file,
                const std::string &p_function,
                int32_t p_line );


            /** Prints an error message

            This method should be not used directly. Please, use the @p
            log_error macro command

            @param [in] p_file is the name of the file from where the log
            message is being printed
            @param [in] p_function is the name of the function from where the
            log message is being printed
            @param [in] p_line is the line number from where the log message
            is being printed

            @return the output stream used to print the message
            */
            logger & error( const std::string &p_file,
                const std::string &p_function,
                int32_t p_line );

            /** Prints a critical message

            This method should be not used directly. Please, use the @p
            log_critical macro command

            @param [in] p_file is the name of the file from where the log
            message is being printed
            @param [in] p_function is the name of the function from where the
            log message is being printed
            @param [in] p_line is the line number from where the log message
            is being printed

            @return the output stream used to print the message
            */
            logger & critical( const std::string &p_file,
                const std::string &p_function,
                int32_t p_line );


            template <typename t>
            logger & operator << ( const t & p ) {
                boost::lock_guard<boost::mutex> l_lock(m_logger_mutex);
                if ( ( m_level != no_log_level ) && ( m_level <= m_current_level ) ) {
                    m_printer->print( p );
                    //cout << p;
                    //cout.flush( );
                    //std::cout << p;
                    //m_st_logged = true;
                }
                return *this;
            }
        private:

            // -----------------------------------------------------------------
            //    logger - Private Internal Types

            // -----------------------------------------------------------------
            //    logger - Private Constructors


            /** No copies allowed */
            logger ( const logger & );

            /** No copies allowed */
            logger ( const logger && );

            // -----------------------------------------------------------------
            //    logger - Private Processors


            /** Commom method used to print the log messages

            @param [in] p_level_str is a string that describes the log level
            @param [in] p_id is the thread id from where the message log is
            being printed
            @param [in] p_file is the name of the file from where the log
            message is being printed
            @param [in] p_function is the name of the function from where the
            log message is being printed
            @param [in] p_line is the line number from where the log message
            is being printed

            */
            void print( const std::string &p_level_str,
                const boost::thread::id & p_id,
                const std::string &p_file,
                const std::string &p_function,
                int32_t p_line );

            // -----------------------------------------------------------------
            //    logger - Private Operators

            /** No copies allowed */
            logger & operator=( const logger & );

            /** No copies allowed */
            logger & operator=( const logger && );


            // -----------------------------------------------------------------
            //    logger - Private Attributes

            printer_ptr_t m_printer;

            /** Current log level */
            level_type m_level;

            /** Mutex to protect concurrent access to the ostream wrapped by the
            @p printer_type type parameter */
            boost::mutex m_logger_mutex;

            /** Indicates that something was already logged */
            //bool m_st_logged;

            level_type m_current_level;

            /** Message formater */
            std::stringstream m_stream;

        };

    }
}

// =============================================================================
//                     I M P L E M E N T A T I O N
// =============================================================================

using namespace tenacitas::log;

// #############################################################################
// logger

//// -----------------------------------------------------------------------------
//template <typename t_printer> 
//boost::mutex    
//    logger<t_printer>::
//    m_logger_mutex;

// -----------------------------------------------------------------------------
template <typename t_printer> 
inline logger<t_printer>::
    logger( printer_ptr_t p_printer, level_type p_level )
    : m_printer( p_printer ),
    m_level( p_level ),
    m_logger_mutex( ),
    //m_st_logged( false ),
    m_current_level( m_level ),
    m_stream( ) { 

        assert( m_printer );
}


// -----------------------------------------------------------------------------
template <typename t_printer> 
inline logger<t_printer>::
    ~logger ( ) { }

//// -----------------------------------------------------------------------------
//template <typename t_printer> 
//inline void
//    logger<t_printer>::
//    level( level_type p_level ) {
//        m_level = p_level;
//}

// -----------------------------------------------------------------------------
template <typename t_printer> 
inline const typename logger<t_printer>::level_type &
    logger<t_printer>::
    level( ) const {
        return m_level;
}

// -----------------------------------------------------------------------------
template <typename t_printer> 
logger<t_printer> &
    logger<t_printer>::
    trace( const std::string &p_file,
    const std::string &p_function,
    int32_t p_line ) {
        
            boost::lock_guard<boost::mutex> l_lock(m_logger_mutex);
            m_current_level = trace_level;
        
        if ( ( m_level != no_log_level ) && ( m_level <= trace_level ) ) {
            print( std::string( "TRACE" ), boost::this_thread::get_id( ),
                p_file, p_function, p_line );
        }
        return *this;
}

// -----------------------------------------------------------------------------
template <typename t_printer> 
logger<t_printer> &
    logger<t_printer>::
    debug( const std::string &p_file,
    const std::string &p_function,
    int32_t p_line ) {
        
            boost::lock_guard<boost::mutex> l_lock(m_logger_mutex);
            m_current_level = debug_level;
        
        if ( ( m_level != no_log_level ) && ( m_level <= debug_level ) ) {
            print( std::string( "DEBUG" ), boost::this_thread::get_id( ),
                p_file, p_function, p_line );
        }               
        return *this;
}

// -----------------------------------------------------------------------------
template <typename t_printer> 
logger<t_printer> &
    logger<t_printer>::
    warn( const std::string &p_file,
    const std::string &p_function,
    int32_t p_line ) {
        
            boost::lock_guard<boost::mutex> l_lock(m_logger_mutex);
            m_current_level = warn_level;
        
        if ( ( m_level != no_log_level ) && ( m_level <= warn_level ) ) {
            print( std::string( "WARN" ), boost::this_thread::get_id( ),
                p_file, p_function, p_line );
        }       

        return *this;
}

// -----------------------------------------------------------------------------
template <typename t_printer> 
logger<t_printer> &
    logger<t_printer>::
    info( const std::string &p_file,
    const std::string &p_function,
    int32_t p_line ) {
        
            boost::lock_guard<boost::mutex> l_lock(m_logger_mutex);
            m_current_level = info_level;
        
        if ( ( m_level != no_log_level ) && ( m_level <= info_level ) ) {
            print( std::string( "INFO" ), boost::this_thread::get_id( ),
                p_file, p_function, p_line );
        }                
        return *this;
}


// -----------------------------------------------------------------------------
template <typename t_printer> 
logger<t_printer> &
    logger<t_printer>::
    error( const std::string &p_file,
    const std::string &p_function,
    int32_t p_line ) {
        
            boost::lock_guard<boost::mutex> l_lock(m_logger_mutex);
            m_current_level = error_level;
        
        if ( ( m_level != no_log_level ) && ( m_level <= error_level ) ) {
            print( std::string( "ERROR" ), boost::this_thread::get_id( ),
                p_file, p_function, p_line );
        }
        return *this;
}


// -----------------------------------------------------------------------------
template <typename t_printer> 
logger<t_printer> &
    logger<t_printer>::
    critical( const std::string &p_file,
    const std::string &p_function,
    int32_t p_line ) {
        
            boost::lock_guard<boost::mutex> l_lock(m_logger_mutex);
            m_current_level = critical_level;
        
        print( std::string( "CRITICAL" ), boost::this_thread::get_id( ),
            p_file, p_function, p_line );
        return *this;
}


// -----------------------------------------------------------------------------
template <typename t_printer> 
void
    logger<t_printer>::
    print( const std::string &p_level_str,
    const boost::thread::id & p_id,
    const std::string &p_file,
    const std::string &p_function,
    int32_t p_line ) {

        

        std::stringstream l_stream;

        //{
        //    boost::lock_guard<boost::mutex> l_lock( m_logger_mutex );
           /* ostream & l_tmp = ( *m_printer )  << "\n";

            l_tmp.flush( );*/
        //}

        l_stream << '\n' << p_level_str << '|';
        if ( p_id == boost::thread::id( ) ) {
            l_stream << '0';
        }
        else {
            l_stream << p_id;
        }

        l_stream << '|' << time(0)
            << '|' << p_function
            << '|' << p_line
            << '|';
//        std::cout << l_stream.str( );
        //(*this) << l_stream.str( );
        m_printer->print( l_stream.str( ) );

}

// #############################################################################
// file_printer

// ---------------------------------------------------------------------------
inline file_printer::
    file_printer( const std::string & p_file_name )
    : m_file( p_file_name, std::ios::out | std::ios::trunc ) {
        assert( m_file );
}


#endif
